#include <stdint.h>
#include <stm32f4xx_ll_gpio.h>

#include "share/utils.h"
#include "_mcu.h"
#include "_mcu_gpio.h"
#include "_mcu_clock.h"
#include "_mcu_spi.h"

static uint32_t _spi_pclk(SPI_TypeDef *port) {    
    switch((uintptr_t)port) {
        case (uintptr_t)SPI1:
    #ifdef SPI4
        case (uintptr_t)SPI4:
    #endif
    #ifdef SPI5
        case (uintptr_t)SPI5:
    #endif
    #ifdef SPI6
        case (uintptr_t)SPI6:
    #endif
            return mcu_clock_get_apb2_freq();
            
        case (uintptr_t)SPI2:
        case (uintptr_t)SPI3:
            return mcu_clock_get_apb1_freq();
            
        default: return 0;
    }
}

static uint32_t _spi_pclk_div(SPI_TypeDef *port, uint32_t baudrate) {
    uint32_t pclk = _spi_pclk(port);
    if(baudrate >= (pclk >> 1)) return LL_SPI_BAUDRATEPRESCALER_DIV2  ; // BaudRate control equal to fPCLK/2  
    if(baudrate >= (pclk >> 2)) return LL_SPI_BAUDRATEPRESCALER_DIV4  ; // BaudRate control equal to fPCLK/2  
    if(baudrate >= (pclk >> 3)) return LL_SPI_BAUDRATEPRESCALER_DIV8  ; // BaudRate control equal to fPCLK/8  
    if(baudrate >= (pclk >> 4)) return LL_SPI_BAUDRATEPRESCALER_DIV16 ; // BaudRate control equal to fPCLK/16 
    if(baudrate >= (pclk >> 5)) return LL_SPI_BAUDRATEPRESCALER_DIV32 ; // BaudRate control equal to fPCLK/32 
    if(baudrate >= (pclk >> 6)) return LL_SPI_BAUDRATEPRESCALER_DIV64 ; // BaudRate control equal to fPCLK/64 
    if(baudrate >= (pclk >> 7)) return LL_SPI_BAUDRATEPRESCALER_DIV128; // BaudRate control equal to fPCLK/128
    /* 最低速率 */               return LL_SPI_BAUDRATEPRESCALER_DIV256; // BaudRate control equal to fPCLK/256
}

std_err_t mcu_spi_init(
	mcu_spi_t *dev,
    mcu_gpio_mux_func_init_t csn_mux_init,
    mcu_gpio_mux_func_init_t sck_mux_init,
    mcu_gpio_mux_func_init_t miso_mux_init,
    mcu_gpio_mux_func_init_t mosi_mux_init,
	uint32_t cpol,
	uint32_t cpha,
	uint32_t baudrate)
{
    if(!dev || !dev->port || !sck_mux_init || !mosi_mux_init || !miso_mux_init) return STD_ERR_INVALID_ARG;
    
    // 初始化 SPI 使用的 GPIO 引脚
    mcu_gpio_num_t gnum; GPIO_TypeDef *gport; uint32_t gpin;
    mcu_gpio_mux_func_init_t pins[] = {csn_mux_init, sck_mux_init, mosi_mux_init, miso_mux_init};
    for(int i=0; i < 4; i++) {
        if(pins[i] == NULL) continue;
        gnum = pins[i](); gport = mcu_gpio_port(gnum); gpin = mcu_gpio_pin(gnum);
        LL_GPIO_SetPinMode      (gport, gpin, LL_GPIO_MODE_ALTERNATE);
        LL_GPIO_SetPinSpeed     (gport, gpin, LL_GPIO_SPEED_FREQ_HIGH); // VERY_HIGH = 90MHz, HIGH = 45MHz ?
        LL_GPIO_SetPinOutputType(gport, gpin, LL_GPIO_OUTPUT_PUSHPULL);
        LL_GPIO_SetPinPull      (gport, gpin, LL_GPIO_PULL_UP);
    }
    
    // 配置 SPI 控制器    
	LL_SPI_InitTypeDef SPI_InitStruct = {0};
    SPI_InitStruct.Mode = LL_SPI_MODE_MASTER;                                       // 设置 SPI 工作模式，设置为主模式
    SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;                          // 设置 SPI 单向或者双向的数据模式:SPI 设置为双线模式
    SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_8BIT;                               // 设置 SPI 的数据大小:SPI 发送接收 8 位帧结构
    SPI_InitStruct.ClockPolarity = cpol;                                            // 串行同步时钟的空闲状态
    SPI_InitStruct.ClockPhase = cpha;                                               // 串行同步时钟的第几个跳变沿（上升或下降）数据被采样
    SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST;                                     // 数据传输从 MSB 位开始
    SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;                  // 关闭硬件 CRC 校验
    SPI_InitStruct.CRCPoly = 7;                                                     // CRC 算法参数，关闭时无效
    SPI_InitStruct.BaudRate = _spi_pclk_div(dev->port, baudrate);                   // 设置 SPI 波特率分频
    SPI_InitStruct.NSS = csn_mux_init ? LL_SPI_NSS_HARD_OUTPUT : LL_SPI_NSS_SOFT;   // NSS 信号由硬件（NSS 管脚）管理; 或 NSS managed internally, NSS pin not used and free.
    LL_SPI_Init(dev->port, &SPI_InitStruct);
    
    LL_SPI_SetStandard(dev->port, LL_SPI_PROTOCOL_MOTOROLA);
    LL_SPI_Enable(dev->port);
	return STD_ERR_OK;
}

std_err_t mcu_spi_set_baudrate(mcu_spi_t *dev, uint32_t baudrate) {
    LL_SPI_Disable(dev->port);                                                  // 关闭 SPI
    LL_SPI_SetBaudRatePrescaler(dev->port, _spi_pclk_div(dev->port, baudrate)); // 设置 SPI 波特率分频
    LL_SPI_Enable(dev->port);                                                   // 使能 SPI
	return STD_ERR_OK;
}

std_err_t mcu_spi_trx(mcu_spi_t *dev, const void *_tx, void *_rx, size_t len, size_t *trxlen) {
    if(!dev || !dev->port || (!_tx && !_rx) || !len) return STD_ERR_INVALID_ARG;
    const uint8_t *tx = (const uint8_t*)_tx;
    uint8_t *rx = (uint8_t*)_rx;
    
    for(size_t i = 0; i < len; i++) {
        // 等待发送缓冲区为空
        while(!LL_SPI_IsActiveFlag_TXE(dev->port));
        // 发送数据
        LL_SPI_TransmitData8(dev->port, tx ? tx[i] : 0xFF);
        
        // 等待接收缓冲区非空
        while(!LL_SPI_IsActiveFlag_RXNE(dev->port));
        if(rx) {
            // 读取数据
            rx[i] = LL_SPI_ReceiveData8(dev->port);
        } else {
            // 注意: 如果不需要接收数据，也要把接收缓冲区中的数据读走（清除掉），否则可能导致下次读写时错误 !!!???
            LL_SPI_ReceiveData8(dev->port);
        }
    }
    
    if(trxlen) *trxlen = len;
	return STD_ERR_OK;
}
